<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <p data-lake-id="u8e925e80" id="u8e925e80" style="text-align: justify"><span data-lake-id="u9d7c2c14" id="u9d7c2c14" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">之前一段时间，业务在线上经常出现频繁的数据库连接池满的报警，报错信息如下：</span></p>
  <pre lang="java"><code>
Caused by: ERR-CODE: [TDDL-4103][ERR_ATOM_CONNECTION_POOL_FULL] 
Pool of DB 'cn-zhxxx_i-xxx_fin_risk_xxx_30xx:33.10.xxx.xx:30xx' is full. 
Message from pool: wait millis 5000, active 10, maxActive 10. 
AppName:FIN_RISK_xxx_APP, Env:ONLINE, UnitName:null. 
</code></pre>
  <p data-lake-id="ub541898e" id="ub541898e" style="text-align: justify"><span data-lake-id="u9026b78a" id="u9026b78a" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">然后去排查了一下SQL耗时，发现有大量的耗时SQL，并且执行耗时和锁耗时差不多是相等的。</span></p>
  <p data-lake-id="u8dc45d98" id="u8dc45d98" style="text-align: justify"><img src="https://cdn.nlark.com/yuque/0/2022/jpeg/5378072/1668684158230-44c1d921-62e1-4f69-bfbd-b2fc5e86a402.jpeg?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_73%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u4996cdda" id="u4996cdda" style="text-align: justify"><span data-lake-id="u28f62aea" id="u28f62aea" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">然后看下这条具体的SQL语句：</span></p>
  <pre lang="java"><code>
UPDATE collection_case 
SET gmt_modified = now(), lock_version = lock_version + ?, hands_count = ?, case_state = ?,  max_ovd_days = ?, case_class = ?, cur_ovd_principal = ?,collection_amount = ? 
WHERE id = ?   AND deleted = ?   AND lock_version = ? 
</code></pre>
  <p data-lake-id="u0a4fa2e6" id="u0a4fa2e6" style="text-align: justify"><span data-lake-id="uaba1c1e8" id="uaba1c1e8" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">其实就是简单的一个更新语句，其中使用了乐观锁进行并发控制。</span></p>
  <p data-lake-id="u50bd81e9" id="u50bd81e9" style="text-align: justify"><span data-lake-id="u770e9f25" id="u770e9f25" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="u68940b76" id="u68940b76" style="text-align: justify"><span data-lake-id="u2e95de4c" id="u2e95de4c" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">为什么乐观锁还会导致大量的锁耗时呢？</span></p>
  <p data-lake-id="ue849c716" id="ue849c716" style="text-align: justify"><span data-lake-id="u2b4b351b" id="u2b4b351b" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="u50dc41e6" id="u50dc41e6" style="text-align: justify"><strong><span data-lake-id="u495c74f4" id="u495c74f4" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">虽然乐观锁是不需要加锁的，通过CAS的方式进行无锁并发控制进行更新的。但是InnoDB的update语句是要加锁的。当并发冲突比较大，发生热点更新的时候，多个update语句就会排队获取锁。</span></strong></p>
  <p data-lake-id="u66428d84" id="u66428d84" style="text-align: justify"><strong><span data-lake-id="ub975152c" id="ub975152c" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span></strong><br></p>
  <p data-lake-id="u082f7daa" id="u082f7daa" style="text-align: justify"><span data-lake-id="u2d249f1f" id="u2d249f1f" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">而这个排队的过程就会占用数据库链接，一旦排队的事务比较多的时候，就会导致数据库连接被耗尽。</span></p>
  <p data-lake-id="ua1c48ae5" id="ua1c48ae5" style="text-align: justify"><span data-lake-id="uefacfa82" id="uefacfa82" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="u6441bf18" id="u6441bf18" style="text-align: justify"><span data-lake-id="u8c8c1d2c" id="u8c8c1d2c" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">这类问题的解决思路有以下几个：</span></p>
  <p data-lake-id="u10c3d9b8" id="u10c3d9b8" style="text-align: justify"><span data-lake-id="u4943e9a1" id="u4943e9a1" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <ul list="u73a2de8b">
   <li fid="u67e6eb34" data-lake-id="u88221f21" id="u88221f21" style="text-align: justify"><span data-lake-id="uc84d7abc" id="uc84d7abc" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">1、基于缓存进行热点数据更新，如Redis。</span></li>
   <li fid="u67e6eb34" data-lake-id="u09948241" id="u09948241" style="text-align: justify"><span data-lake-id="uef498b70" id="uef498b70" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">2、通过异步更新的方式，将高并发的更新削峰填谷掉。</span></li>
   <li fid="u67e6eb34" data-lake-id="u460d1115" id="u460d1115" style="text-align: justify"><span data-lake-id="u896c27cc" id="u896c27cc" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">3、将热点数据进行拆分，分散到不同的库、不同的表中，减少并发冲突。</span></li>
   <li fid="u67e6eb34" data-lake-id="uf78951b3" id="uf78951b3" style="text-align: justify"><span data-lake-id="u751cc203" id="u751cc203" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">4、合并更新请求，通过批量执行的方式来降低冲突。（比如你有10条增加积分的操作要执行，那么你就可以算出这十条一共要加多少积分，一次性加上去）</span></li>
  </ul>
  <p data-lake-id="u47a2c998" id="u47a2c998" style="text-align: justify"><span data-lake-id="ubb2e51f8" id="ubb2e51f8" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="u4c8fcd01" id="u4c8fcd01" style="text-align: justify"><span data-lake-id="u7f86d1ef" id="u7f86d1ef" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">其中，第2个和第4个方案，存在一定的延迟性，会把实时更新变更异步更新，存在一定的数据实时性问题。</span></p>
  <p data-lake-id="u94aeece6" id="u94aeece6" style="text-align: justify"><span data-lake-id="u5687699f" id="u5687699f" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="u6e85253c" id="u6e85253c" style="text-align: justify"><span data-lake-id="ubfe7f32b" id="ubfe7f32b" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">第1个和第3个方案，实现起来成本比较高，但是相对来说更加完整一点。</span></p>
  <p data-lake-id="u49b07581" id="u49b07581" style="text-align: justify"><span data-lake-id="ufe8ea76f" id="ufe8ea76f" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="u89b3d0a3" id="u89b3d0a3" style="text-align: justify"><span data-lake-id="ube95809b" id="ube95809b" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">根据我们的实际业务场景，我们选择了第4个方案，将更新操作进行合并，批量执行。</span></p>
  <p data-lake-id="uf5edd3c3" id="uf5edd3c3" style="text-align: justify"><span data-lake-id="ud036db69" id="ud036db69" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="uf6b3356b" id="uf6b3356b" style="text-align: justify"><span data-lake-id="u2f676c3f" id="u2f676c3f" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">比如本来需要100个并发都给用户增加积分，那么改成10分钟更新一次，一次就把10分钟中内的所有需要增加的积分汇总之后一次更新用户积分表即可。</span></p>
 </body>
</html>